﻿using RockBus.Logging.Interfaces;
using RockBus.PerformanceCounters.Interfaces;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace RockBus.PerformanceCounters
{
    /// <summary>
    /// Creates OperationCounters for the CategoryName, provided in the constructor.
    /// </summary>
    public class PerformanceCounterFactory : IPerformanceCounterFactory
    {
        private const string UpTimeCounterName = "UpTime";

        private static readonly ConcurrentDictionary<string, OperationCounterInternal> _counterDictionary = new ConcurrentDictionary<string, OperationCounterInternal>();

        public PerformanceCounterFactory(ILogger logger, IPerformanceCounterConfiguration performanceCounterConfiguration)
        {
            this.Logger = logger;
            this.Configuration = performanceCounterConfiguration;

            //var initializationTask = this.EnsurePerformanceCounterInstallationAsync();
            //initializationTask.ContinueWith(this.PostInitialization);

            this._upTimeCounter = new PerformanceCounter(this.Configuration.CategoryName, UpTimeCounterName, this.Configuration.InstanceName, false);
            this._updateTimer = new Timer(this.UpdateTimerCallback, null, this.Configuration.UpdateIntervalMs, this.Configuration.UpdateIntervalMs);
            this._category = new PerformanceCounterCategory(this.Configuration.CategoryName);
            
            IsInitialised = true;
        }

        public ILogger Logger { get; private set; }

        public IPerformanceCounterConfiguration Configuration { get; private set; }

        private Task<PerformanceCounterCategory> InitializationTask { get; set; }

        private PerformanceCounterCategory _category;

        public PerformanceCounterCategory Category { get { return this._category; } }

        private Timer _updateTimer;

        private PerformanceCounter _upTimeCounter;

        #region IPerformanceCounterFactory

        public bool IsInitialised { get; set; }

        public IOperationCounter CreateOperationCounter(string operationName)
        {
            if (this._category == null)
            {
                return new OperationCounterPlaceholder();
            }

            var oci = _counterDictionary.GetOrAdd(operationName, (on) =>
            {
                return new OperationCounterInternal(this.Configuration.CategoryName, on, this.Configuration.InstanceName, false);
            });
            return new OperationCounter(oci);
        }

        public IEnumerable<IReadOnlyOperationCounter> GetReadOnlyOperationCounters()
        {
            if (this._category == null)
            {
                yield break;
            }

            foreach (var operationName in this.Configuration.OperationNames)
            {
                var oci = new OperationCounterInternal(this.Configuration.CategoryName, operationName, this.Configuration.InstanceName, true);
                yield return new ReadOnlyOperationCounter(oci);
            }
        }

                
        #endregion

        private Task<PerformanceCounterCategory> EnsurePerformanceCounterInstallationAsync()
        {
            return Task.Run<PerformanceCounterCategory>(() =>
            {
                if (PerformanceCounterCategory.Exists(this.Configuration.CategoryName))
                {
                    // Retrieve the category
                    var categories = PerformanceCounterCategory.GetCategories();
                    var category = categories.FirstOrDefault((c) => c.CategoryName.Equals(this.Configuration.CategoryName));
                    if (category == null)
                    {
                        return this.InstallPerformanceCounters();
                    }

                    // Ensure all counters exist, if not, recreate the entire category
                    foreach (var operationName in this.Configuration.OperationNames)
                    {
                        foreach (var counterName in RockBusPerformanceCounterMetadata.GetCounterNames(operationName))
                        {
                            if (!PerformanceCounterCategory.CounterExists(counterName, this.Configuration.CategoryName))
                            {
                                this.Logger.LogWarning(MethodBase.GetCurrentMethod(), "Category {0} has a missing PerformanceCounter: {1}.", this.Configuration.CategoryName, counterName);
                                return this.InstallPerformanceCounters();
                            }
                        }
                    }                                       

                    if (!PerformanceCounterCategory.CounterExists(UpTimeCounterName, this.Configuration.CategoryName))
                    {
                        this.Logger.LogWarning(MethodBase.GetCurrentMethod(), "Category {0} has a missing PerformanceCounter: {1}.", this.Configuration.CategoryName, UpTimeCounterName);
                        return this.InstallPerformanceCounters();
                    }

                    this.Logger.LogInfo(MethodBase.GetCurrentMethod(), "PerformanceCounterCategory installation was already correct for Category {0}.", this.Configuration.CategoryName);
                    return category;
                }
                else
                {
                    return this.InstallPerformanceCounters();
                }
            });
        }

        private PerformanceCounterCategory InstallPerformanceCounters()
        {
            this.Logger.LogInfo(MethodBase.GetCurrentMethod(), "(Re)Installing PerformanceCounterCategory {0}.", this.Configuration.CategoryName);

            if (PerformanceCounterCategory.Exists(this.Configuration.CategoryName))
            {
                PerformanceCounterCategory.Delete(this.Configuration.CategoryName);
            }

            var ccdList = new List<CounterCreationData>();
            foreach (var operationName in this.Configuration.OperationNames)
            {
                var ccds = RockBusPerformanceCounterMetadata.GetCounterCreationDatas(operationName);
                ccdList.AddRange(ccds);
            }
                        
            var ccdUpTime = new CounterCreationData(
                UpTimeCounterName,
                "Number of seconds this application has been running.",
                PerformanceCounterType.NumberOfItems64);
            ccdList.Add(ccdUpTime);

            var ccdc = new CounterCreationDataCollection(ccdList.ToArray());
            return PerformanceCounterCategory.Create(
                this.Configuration.CategoryName,
                this.Configuration.CategoryHelp,
                PerformanceCounterCategoryType.MultiInstance,
                ccdc);
        }

        private void PostInitialization(Task<PerformanceCounterCategory> antecedent)
        {
            if (antecedent.Exception != null)
            {
                this.Logger.LogError(MethodBase.GetCurrentMethod(), antecedent.Exception.ToString());
                return;
            }

            this._upTimeCounter = new PerformanceCounter(this.Configuration.CategoryName, UpTimeCounterName, this.Configuration.InstanceName, false);
            this._updateTimer = new Timer(this.UpdateTimerCallback, null, this.Configuration.UpdateIntervalMs, this.Configuration.UpdateIntervalMs);
            this._category = antecedent.Result;
            
            IsInitialised = true;
        }
               
        private void UpdateTimerCallback(object state)
        {
            var upTime = DateTime.Now - this.Configuration.ProcessStartTime;
            this._upTimeCounter.RawValue = (long)upTime.TotalSeconds;
        }


        
    }
        
}
